package br.brpv.silvestrin.vendas.domain;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.ListIterator;
import java.util.StringTokenizer;

import br.brpv.silvestrin.vendas.R;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.TextView;

public class OrderAdapter extends ArrayAdapter<Order> {
    private Context mContext;
    private int layoutResourceId = R.layout.list_view_rows_orders;
    private ArrayList<Order> mOrders = null, mDisplayedOrders = null;

    private Boolean mOutOfSync = false;
    private boolean mNotifyOnChange = false;

    private final Object mFilterLock = new Object();

    private Filter filter = null;

    public OrderAdapter(Context context, ArrayList<Order> data){
    	super(context, R.layout.list_view_rows_orders, data);

        this.mContext = context;
        this.mOrders = data;

        mOrders = (data != null) ? data : new ArrayList<Order>();
        mDisplayedOrders = new ArrayList<Order>(mOrders);
    }
    
    public Context getContext() {
        return mContext;
    }

    protected ArrayList<Order> getObjects() {
        return mOrders;
    }

    protected Object getFilterLock() {
        return mFilterLock;
    }

    protected boolean isOutOfSync() {
        return mOutOfSync;
    }

    protected void setOutOfSync(boolean outOfSync) {
        mOutOfSync = outOfSync;
    }

    public void setNotifyOnChange(boolean notifyOnChange) {
        mNotifyOnChange = notifyOnChange;
    }

    public void add(Order object) {
        synchronized (mOrders) {
        	mOrders.add(object);
        }

        if (mNotifyOnChange) notifyDataSetChanged();
    }

    public void addAll(Collection<? extends Order> collection) {
        synchronized (mOrders) {
        	mOrders.addAll(collection);
        }

        if (mNotifyOnChange) notifyDataSetChanged();
    }

    public void addAll(Order ... objects) {
        synchronized (mOrders) {
            for (Order object : objects)
            	mOrders.add(object);
        }

        if (mNotifyOnChange) notifyDataSetChanged();
    }

    public void insert(Order object, int index) {
        synchronized (mOrders) {
        	mOrders.add(index, object);
        }

        if (mNotifyOnChange) notifyDataSetChanged();
    }

    public void remove(Order object) {
        synchronized (mOrders) {
        	mOrders.remove(object);
        }

        if (mNotifyOnChange) notifyDataSetChanged();
    }

    public void clear() {
        synchronized (mOrders) {
        	mOrders = new ArrayList<Order>();
        }

        if (mNotifyOnChange) notifyDataSetChanged();
    }

    public void sort(Comparator<? super Order> comparator) {
        synchronized (mOrders) {
            Collections.sort(mOrders, comparator);
        }

        if (mNotifyOnChange) notifyDataSetChanged();
    }

    @Override
    public void notifyDataSetChanged() {
        boolean reapplyFilter;

        synchronized (mFilterLock) {
            reapplyFilter = mOutOfSync = (mLastFilter != null);
        }

        if (reapplyFilter) {
            /* It would be amazing to only apply the filter to the
             * new elements, but since the collection could have
             * suffered unknown modifications, we can't.
             */

            getFilter().filter(mLastFilter);

        } else {
            synchronized (mOrders) {
                mDisplayedOrders = new ArrayList<Order>(mOrders);
            }
        }

        doNotifyDataSetChanged();
    }

    protected void doNotifyDataSetChanged() {
        super.notifyDataSetChanged();
    }

    @Override
    public int getCount() {
        return mDisplayedOrders.size();
    }

    @Override
    public Order getItem(int position) {
        return mDisplayedOrders.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View row = convertView;
        OrderHolder holder = null;

        if(row == null) {
            LayoutInflater inflater = ((Activity)mContext).getLayoutInflater();
            row = inflater.inflate(layoutResourceId, parent, false);

            holder = new OrderHolder();
            holder.orderId = (TextView)row.findViewById(R.id.saved_orders_order_id);
            holder.dateEmiss = (TextView)row.findViewById(R.id.saved_orders_order_date);
            holder.client = (TextView)row.findViewById(R.id.saved_orders_order_client);
            holder.establishment = (TextView)row.findViewById(R.id.saved_orders_order_establishment);

            row.setTag(holder);
        } else {
            holder = (OrderHolder)row.getTag();
        }

        Order order = getItem(position);
        if(order != null) {
            if(holder.orderId!=null)holder.orderId.setText(String.valueOf(order.getId()));
            if(holder.dateEmiss!=null)holder.dateEmiss.setText(order.getDateEmiss());
            if(holder.client!=null)holder.client.setText(order.getNomeCli());
            if(holder.establishment!=null)holder.establishment.setText(order.getNomeEstab());
        }
        
        return row;
    }

    private static class OrderHolder {
        TextView orderId;
        TextView dateEmiss;
        TextView client;
        TextView establishment;
    }

    @Override
    public Filter getFilter() {
        if(filter == null)
            filter = new OrderFilter();
        return filter;
    }

    private CharSequence mLastFilter = "";

    private class OrderFilter extends Filter {
        @Override
        protected FilterResults performFiltering(CharSequence constraintSeq) {
            ArrayList<Order> filteredObjects;

            synchronized (mFilterLock) {
                if (!mOutOfSync && mLastFilter != null && mLastFilter.equals(constraintSeq))
                    return null;

                mLastFilter = constraintSeq;
            }

            synchronized (mOrders) {
                /* We'll make a copy of the list, so we can release the lock
                 * as soon as possible.partial
                 */
                filteredObjects = new ArrayList<Order>(mOrders);
            }

            if (constraintSeq == null || constraintSeq.length() == 0) /* Part of the Filter contract */
                return resultsFromList(filteredObjects);

            String constraint = constraintSeq.toString().toLowerCase();

            ListIterator<Order> it = filteredObjects.listIterator();

            while (it.hasNext()) {
            	Order item = it.next();
                StringTokenizer tokenizer = new StringTokenizer(constraint, " ");

                String orderId = String.valueOf(item.getId()).toLowerCase();
                String dateEmiss = item.getDateEmiss().toLowerCase();
                String client = item.getNomeCli().toLowerCase();
                String establishment = item.getNomeEstab().toLowerCase();

                boolean match = true;

                while(tokenizer.hasMoreTokens() && match) {
                    String token = tokenizer.nextToken();

                    if(!orderId.contains(token) && !dateEmiss.contains(token) && !client.contains(token) && !establishment.contains(token)) {
                        match = false;
                    }
                }

                if (!match) it.remove();
            }

            return resultsFromList(filteredObjects);
        }

        @SuppressWarnings("unchecked")
		@Override
        protected void publishResults(CharSequence constraint, FilterResults results) {
            if (results != null) {
                mDisplayedOrders = (ArrayList<Order>) results.values;
            }

            doNotifyDataSetChanged();
        }

        protected FilterResults resultsFromList(ArrayList<Order> list) {
            FilterResults fr = new FilterResults();

            fr.values = list;
            fr.count = list.size();

            return fr;
        }
    }
}